using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using Microsoft.Win32;
using TrackMe.Apps.Integration.TFS;
using TrackMe.Apps.VM;
using ITfsServer = TrackMe.Dto.ITfsServer;

namespace TrackMe.Apps
{

	[Export]
	public class TfsSyncControl
	{
		private class ProgressTask
		{
		    internal int ServerId;
			internal int IssueId;
			internal TimeSpan Completed;
		    internal int JobId;
		}

		private class SyncTask
		{
			internal ITfsServer Server;
			internal IList<ITfsWorkItem> WorkItems;
			internal DateTime SyncTime;
			internal int[] IssuesToFetch;
			internal ProgressTask[] Progress;
		}
		
		delegate void AsyncRun(SyncTask task);

		private int runCount;
		private DateTime lastRun = DateTime.Now;
		private readonly object lastRunLock = new object();
		private readonly List<Action> doOnComplete = new List<Action>();

		private readonly IDatabase db;
		private readonly List<ProgressTask> pendingProgress = new List<ProgressTask>();
	    private readonly List<int> clockoutInProgress = new List<int>();

	    [ImportingConstructor]
		public TfsSyncControl(IDatabase database)
		{
			db = database;
		}

		const int SecondsBetweenPoll = 180;

		private bool IsBusy
		{
			get
			{
				return runCount > 0; // 32 bit operation is atomic so no need to thread guard
			}
		}

		public void DoOnComplete(Action a)
		{
			doOnComplete.Add(a);
		}

		public void Heartbeat(bool immediate)
		{
			if (IsBusy) 
				return;
			if (immediate || (DateTime.Now - lastRun).TotalSeconds > SecondsBetweenPoll )
			{
				Interlocked.Increment(ref runCount);
				try
				{
					IList<ITfsServer> serverList = db.TfsServerList();
					if (serverList.Count == 0)
					{
						var root = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\VisualStudio\11.0\TeamFoundation\Instances");
						if (root != null)
						{
							foreach (string instance in root.GetSubKeyNames())
							{
								var key = root.OpenSubKey(instance);
								if (key != null)
								{
									var item = AutoIntf.New<ITfsServer>();
									item.Url = (string) key.GetValue("Uri");
									item.Name = instance;
									db.SaveTfsServer(item);
								}
							}
						}
						serverList = db.TfsServerList();
					}

				    if (serverList.Count == 0)
				    {
				        lock (lastRunLock)
				            lastRun = DateTime.Now;
				    }
				    else
				    {
                        var pendingToGo = pendingProgress.ToArray();
                        pendingProgress.Clear();
                        foreach (var server in serverList)
				        {
				            var task = new SyncTask
				            {
				                Server = server,
				                Progress = pendingToGo.Where(p => p.ServerId == server.Id).ToArray()
				            };
							Interlocked.Increment(ref runCount);
			                new AsyncRun(CheckServer).BeginInvoke(task, OnComplete, task);
				        }
				    }
				}
				finally
				{
					Interlocked.Decrement(ref runCount);
				}
			}
		}

		// In worker thread
		private static void CheckServer(SyncTask task)
		{
			task.SyncTime = DateTime.Now;
			var conn = new TfsConnection(task.Server.Url);
			var minDateTime = new DateTime(2000, 1, 1);
			var lastSyncTime = task.Server.LastSyncTime < minDateTime ? minDateTime : task.Server.LastSyncTime;
			if (task.IssuesToFetch != null)
			{
				foreach (var item in conn.FetchItems(task.IssuesToFetch))
					task.WorkItems.Insert(0, item);
				task.IssuesToFetch = null;
			}
			else
				task.WorkItems = conn.WorkItems(lastSyncTime);
			foreach (var progressTask in task.Progress)
			{
				conn.UpdateWorkload(progressTask.IssueId, progressTask.Completed);
			}
				
		}

		private void OnComplete(IAsyncResult ar)
		{
			try
			{
				var task = (SyncTask)ar.AsyncState;
				ProgressTask[] progressTasks = task.Progress;
				task.Progress = new ProgressTask[0];

				var clockedOut = progressTasks.Select(t => t.JobId).ToArray();
				if (clockedOut.Any())
					db.ClockOutDone(clockedOut);

				if (task.WorkItems != null && task.WorkItems.Count > 0)
				{
					task.WorkItems = task.WorkItems.OrderBy(t => t.Id).ToList();
					int[] parentIds = task.WorkItems.Where(i => i.ParentId > 0).Select(i => i.ParentId).ToArray();
					int[] fetchedIds = task.WorkItems.Select(i => i.Id).ToArray();
					task.IssuesToFetch = db.FindAbsentTasks(parentIds.Except(fetchedIds).ToArray());
					if (task.IssuesToFetch.Length > 0)
					{
						Interlocked.Increment(ref runCount);
						new AsyncRun(CheckServer).BeginInvoke(task, OnComplete, task);
						return;
					}
					db.ImportTfsItems(task.Server.Id, task.SyncTime, task.WorkItems);
				
				}

			    var actions = doOnComplete.ToArray();
				doOnComplete.Clear();
				foreach (var action in actions)
					action();
			}
			finally
			{
				lock (lastRunLock)
					lastRun = DateTime.Now;
				Interlocked.Decrement(ref runCount);
			}
			// todo notify
		}

		public void UpdateProgress(int issueId, TimeSpan completed, int jobId, int serverId)
		{
		    if (clockoutInProgress.Contains(jobId))
		        return;
            clockoutInProgress.Add(jobId);
			pendingProgress.Add(new ProgressTask { IssueId = issueId, Completed = completed, JobId = jobId, ServerId = serverId});
		}
	}
}